跳到主要内容

系统设计学习笔记 - 1

· 阅读需 13 分钟

设计一个支持数百万用户的系统是一个极具挑战性的过程,需要不断的改进和优化。

单服务器架构

千里之行,始于足下,构建复杂系统也是如此。首先,我们从一个简单的单服务器架构开始,所有组件(如 web 应用、数据库、缓存等)都运行在一台服务器上(图 1-1)。

Imgur

请求流程与流量来源

理解这种架构,我们可以从请求流程和流量来源入手。

请求流程(图 1-2):

  1. 用户通过域名(如 api.mysite.com)访问网站,域名系统(DNS)通常由第三方提供,而非自建。
  2. DNS 返回 IP 地址(例如:15.125.23.214)给浏览器或移动应用。
  3. 获取 IP 地址后,发送 HTTP 请求到 Web 服务器。
  4. Web 服务器返回 HTML 页面或 JSON 响应进行渲染。

流量来源:

  • Web 应用: 使用服务器端语言(如 Java、Python 等)处理业务逻辑和存储,客户端语言(如HTML 和 JavaScript)用于展示。
  • 移动应用: 使用 HTTP 协议与 Web 服务器通信,常用 JSON 作为 API 响应格式。

数据库

随着用户基数的增长,一台服务器已无法满足需求,我们需要将 Web / 移动流量和数据库分离成独立的服务器(图 1-3)。这种分离使得 Web 层和数据层可以独立扩展。

选择哪种数据库?

我们可以选择传统关系型数据库或非关系型数据库。

关系型数据库(RDBMS/SQL): 如 MySQL、Oracle、PostgreSQL 等,数据以表格和行的形式存储,可以进行 SQL 跨表查询。

非关系型数据库(NoSQL): 如 CouchDB、Neo4j、Cassandra、HBase、Amazon DynamoDB 等,分为键值存储、图存储、列存储和文档存储四类。通常不支持联表查询。

对于大多数开发者来说,关系型数据库是首选,因为其成熟可靠。但在某些特殊场景下,非关系型数据库可能更合适:

  • 需要超低延迟。
  • 数据非结构化或无关系。
  • 只需序列化和反序列化数据(如 JSON、XML、YAML 等)。
  • 需要存储大量数据。

垂直扩展 vs. 水平扩展

垂直扩展(scale-up): 增加服务器的 CPU、内存等资源。

水平扩展(scale-out): 增加服务器数量。

当流量较小时,垂直扩展是个好选择,因为其简单直接。但它有明显的局限性:

  • 垂直扩展有硬性限制,无法无限增加资源。
  • 无法实现故障转移和冗余,单服务器故障会导致网站完全瘫痪。

对于大规模应用,水平扩展更为理想。为解决直接连接 Web 服务器的弊端,我们引入了负载均衡。

负载均衡

负载均衡器将流量均匀分配到多个 Web 服务器,用户通过负载均衡器的公共IP访问Web服务器(图 1-4)。这种设计提高了系统的可用性和容错性。

负载均衡解决了无故障转移问题,并提升了 Web 层的可用性:

  • 当服务器 1 离线时,流量自动转向服务器 2。
  • 如果流量激增,只需增加更多服务器,负载均衡器自动分配请求。

数据库复制

数据库复制通常采用主从复制模式(图 1-5),主数据库负责写操作,从数据库负责读操作。

数据库复制的优点:

  • 性能提升: 读操作分散到多个从数据库,提高并行处理能力。
  • 可靠性: 数据在多个位置复制,即使发生灾害也不必担心数据丢失。
  • 高可用性: 数据库离线时,可访问其他数据库服务器的数据。

在生产系统中,提升新的主数据库较为复杂,因为从数据库中的数据可能不是最新的,需要通过数据恢复脚本更新缺失的数据。虽然多主复制和环形复制等其他复制方法可以帮助解决这个问题,但它们的设置较为复杂,超出了本书的讨论范围。

缓存层

缓存是一种临时存储,存储常用数据以提高访问速度。独立的缓存层(图 1-7)可减轻数据库负载,并能独立扩展。常用的缓存策略包括读穿缓存等。

缓存系统的考虑因素:

  • 使用时机: 数据频繁读取但不经常修改时使用缓存。重要数据应保存在持久化存储中,因为缓存服务器重启后数据会丢失。
  • 过期策略: 实现数据过期策略,以防止缓存数据永久存储在内存中。过期时间不宜过短或过长,以平衡频繁加载数据和数据陈旧的问题。
  • 一致性: 保持数据存储与缓存的一致性是一个挑战,尤其是在跨区域扩展时。
  • 故障缓解: 单一缓存服务器是潜在的单点故障(SPOF),建议在不同数据中心部署多个缓存服务器,并适当预留内存缓冲。
  • 淘汰策略: 缓存满时,采用LRU(最近最少使用)等策略淘汰旧数据。

内容分发网络(CDN)

CDN 通过地理分布的服务器网络分发静态内容,如图片、视频、CSS、JavaScript 文件等。用户访问网站时,最近的 CDN 服务器提供静态内容,从而加快加载速度(图 1-10)。

使用CDN的考虑因素:

  • 成本: CDN 由第三方提供,按数据传输量收费。缓存不常用的资源无显著益处,应将其移出 CDN。
  • 缓存过期设置: 为时效性内容设置适当的缓存过期时间,避免内容过期或频繁重新加载。
  • CDN 故障应对: 处理 CDN 故障时,客户端应能从源站点请求资源。
  • 文件失效: 通过 API 或对象版本控制失效 CDN 缓存文件。

无状态Web层

为实现水平扩展,我们需要将状态(如用户会话数据)移出Web层,存储在持久化存储中。这使得Web层成为无状态层,易于扩展(图 1-14)。

数据中心

为了提高可用性和用户体验,我们需要支持多个数据中心(图 1-15)。geoDNS 服务根据用户位置将流量分配到最近的数据中心。若某数据中心故障,流量自动重定向到其他数据中心。

实现多数据中心架构需要解决以下技术挑战:

  • 流量重定向: 使用 GeoDNS 将流量导向最近的数据中心。
  • 数据同步: 通过多数据中心复制策略,确保不同区域用户的数据一致性。
  • 测试与部署: 使用自动化部署工具保持各数据中心服务一致性。

消息队列

消息队列支持异步通信,使系统各组件解耦,便于独立扩展和故障恢复(图 1-17)。例如,图 1-18展示了照片处理任务的消息队列架构,生产者将任务发布到队列,消费者从队列中获取任务进行处理。

日志、监控、自动化

随着系统规模的扩大,日志、监控和自动化工具变得必不可少。

日志: 监控错误日志有助于识别系统中的错误和问题。可以在每台服务器上监控错误日志,或使用工具将其汇总到集中服务中,便于搜索和查看。

监控: 收集不同类型的指标有助于获取业务洞察和了解系统健康状况。常用的指标包括:

  • 主机级别指标: CPU、内存、磁盘 I/O 等。
  • 汇总级别指标: 如整个数据库层、缓存层的性能。
  • 关键业务指标: 日活跃用户、留存率、收入等。

自动化: 随着系统规模和复杂度的增加,构建或利用自动化工具提高生产力至关重要。持续集成是一个好实践,每次代码提交通过自动化验证,帮助团队早期发现问题。自动化构建、测试、部署流程也能显著提高开发者的生产力。

添加消息队列和工具

图 1-19展示了更新后的设计。由于空间限制,仅展示了一个数据中心。设计中包括消息队列、日志、监控、度量和自动化工具,使系统更加松耦合和容错。

数据库扩展

数据库扩展主要有垂直扩展和水平扩展两种方式。

垂直扩展: 增加数据库服务器的资源,如 CPU、内存等。但有硬性限制和单点故障风险。

水平扩展(分片): 将大数据库分为多个小的部分(分片)。选择合适的分片键(如用户 ID)是关键,确保数据均匀分布(图 1-22)。

在图 1-23中,我们对数据库进行了分片,以支持快速增长的数据流量,同时将一些非关系型功能迁移到NoSQL数据存储中,以减轻数据库负载。

结论

扩展系统是一个迭代过程,需要不断优化和引入新策略。以下是支持数百万用户的系统设计要点:

  • Web 层无状态化
  • 各层冗余设计
  • 尽量缓存数据
  • 支持多数据中心
  • 静态资源托管在 CDN
  • 数据层通过分片扩展
  • 各层独立服务化
  • 监控系统并使用自动化工具